﻿using System;


namespace RoughJs.Ast
{
    public class ObjArray
    {
        private int size1;

        private bool sealed1;

        private static int FIELDS_STORE_SIZE = 5;
        private Object f0, f1, f2, f3, f4;
        private Object[] data;

        public ObjArray() { }

        public bool isSealed()
        {
            return sealed1;
        }

        public void seal()
        {
            sealed1 = true;
        }

        public bool isEmpty()
        {
            return size1 == 0;
        }

        public int size()
        {
            return size1;
        }

        public void setSize(int newSize)
        {
            if (newSize < 0) throw new Exception();
            if (sealed1) throw onSeledMutation();
            int N = size1;
            if (newSize < N)
            {
                for (int i = newSize; i != N; ++i)
                {
                    setImpl(i, null);
                }
            }
            else if (newSize > N)
            {
                if (newSize > FIELDS_STORE_SIZE)
                {
                    ensureCapacity(newSize);
                }
            }
            size1 = newSize;
        }

        public Object get(int index)
        {
            if (!(0 <= index && index < size1)) throw onInvalidIndex(index, size1);
            return getImpl(index);
        }

        public void set(int index, Object value)
        {
            if (!(0 <= index && index < size1)) throw onInvalidIndex(index, size1);
            if (sealed1) throw onSeledMutation();
            setImpl(index, value);
        }

        private Object getImpl(int index)
        {
            switch (index)
            {
                case 0: return f0;
                case 1: return f1;
                case 2: return f2;
                case 3: return f3;
                case 4: return f4;
            }
            return data[index - FIELDS_STORE_SIZE];
        }

        private void setImpl(int index, Object value)
        {
            switch (index)
            {
                case 0: f0 = value; break;
                case 1: f1 = value; break;
                case 2: f2 = value; break;
                case 3: f3 = value; break;
                case 4: f4 = value; break;
                default: data[index - FIELDS_STORE_SIZE] = value; break;
            }

        }

        public int indexOf(Object obj)
        {
            int N = size1;
            for (int i = 0; i != N; ++i)
            {
                Object current = getImpl(i);
                if (current == obj || (current != null && current.Equals(obj)))
                {
                    return i;
                }
            }
            return -1;
        }

        public int lastIndexOf(Object obj)
        {
            for (int i = size1; i != 0; )
            {
                --i;
                Object current = getImpl(i);
                if (current == obj || (current != null && current.Equals(obj)))
                {
                    return i;
                }
            }
            return -1;
        }

        public Object peek()
        {
            int N = size1;
            if (N == 0) throw onEmptyStackTopRead();
            return getImpl(N - 1);
        }

        public Object pop()
        {
            if (sealed1) throw onSeledMutation();
            int N = size1;
            --N;
            Object top;
            switch (N)
            {
                case -1: throw onEmptyStackTopRead();
                case 0: top = f0; f0 = null; break;
                case 1: top = f1; f1 = null; break;
                case 2: top = f2; f2 = null; break;
                case 3: top = f3; f3 = null; break;
                case 4: top = f4; f4 = null; break;
                default:
                    top = data[N - FIELDS_STORE_SIZE];
                    data[N - FIELDS_STORE_SIZE] = null;
                    break;
            }
            size1 = N;
            return top;
        }

        public void push(Object value)
        {
            add(value);
        }

        public void add(Object value)
        {
            if (sealed1) throw onSeledMutation();
            int N = size1;
            if (N >= FIELDS_STORE_SIZE)
            {
                ensureCapacity(N + 1);
            }
            size1 = N + 1;
            setImpl(N, value);
        }

        public void add(int index, Object value)
        {
            int N = size1;
            if (!(0 <= index && index <= N)) throw onInvalidIndex(index, N + 1);
            if (sealed1) throw onSeledMutation();
            Object tmp;
            switch (index)
            {
                case 0:
                    if (N == 0) { f0 = value; break; }
                    tmp = f0; f0 = value; value = tmp;
                    goto case 1;
                case 1:
                    if (N == 1) { f1 = value; break; }
                    tmp = f1; f1 = value; value = tmp;
                    goto case 2;
                case 2:
                    if (N == 2) { f2 = value; break; }
                    tmp = f2; f2 = value; value = tmp;
                    goto case 3;
                case 3:
                    if (N == 3) { f3 = value; break; }
                    tmp = f3; f3 = value; value = tmp;
                    goto case 4;
                case 4:
                    if (N == 4) { f4 = value; break; }
                    tmp = f4; f4 = value; value = tmp;

                    index = FIELDS_STORE_SIZE;
                    goto default;
                default:
                    ensureCapacity(N + 1);
                    if (index != N)
                    {
                        Array.Copy(data, index - FIELDS_STORE_SIZE,
                                         data, index - FIELDS_STORE_SIZE + 1,
                                         N - index);
                        //System.arraycopy(data, index - FIELDS_STORE_SIZE,
                        //                 data, index - FIELDS_STORE_SIZE + 1,
                        //                 N - index);
                    }
                    data[index - FIELDS_STORE_SIZE] = value;
                    break;
            }

            //if (index == 0)
            //{
            //    if (N == 0 && index <= 0) { f0 = value; }
            //    else
            //    {
            //        tmp = f0; f0 = value; value = tmp;

            //        if (N == 0 && index <= 0) { f0 = value; }
            //        else { tmp = f0; f0 = value; value = tmp; }
            //        if (N == 1 && index <= 1) { f1 = value; }
            //        else { tmp = f1; f1 = value; value = tmp; }
            //        if (N == 2 && index <= 2) { f2 = value; }
            //        else { tmp = f2; f2 = value; value = tmp; }
            //        if (N == 3 && index <= 3) { f3 = value; }
            //        else { tmp = f3; f3 = value; value = tmp; }
            //        if (N == 4 && index <= 4) { f4 = value; }
            //        else
            //        {
            //            tmp = f4; f4 = value; value = tmp;

            //            index = FIELDS_STORE_SIZE;
            //        }

            //        ensureCapacity(N + 1);
            //        if (index != N)
            //        {
            //            Array.Copy(data, index - FIELDS_STORE_SIZE,
            //                             data, index - FIELDS_STORE_SIZE + 1,
            //                             N - index);
            //            //System.arraycopy(data, index - FIELDS_STORE_SIZE,
            //            //                 data, index - FIELDS_STORE_SIZE + 1,
            //            //                 N - index);
            //        }
            //        data[index - FIELDS_STORE_SIZE] = value;

            //    }
            //}
            //else if (index == 1)
            //{

            //}
            //else if (index == 2)
            //{

            //}
            //else if (index == 3)
            //{

            //}
            //else if (index == 4)
            //{

            //}
            //else
            //{

            //}
            //if (N == 0 && index <= 0) { f0 = value; }
            //else { tmp = f0; f0 = value; value = tmp; }
            //if (N == 1 && index <= 1) { f1 = value; }
            //else { tmp = f1; f1 = value; value = tmp; }
            //if (N == 2 && index <= 2) { f2 = value; }
            //else { tmp = f2; f2 = value; value = tmp; }
            //if (N == 3 && index <= 3) { f3 = value; }
            //else { tmp = f3; f3 = value; value = tmp; }
            //if (N == 4 && index <= 4) { f4 = value; }
            //else
            //{
            //    tmp = f4; f4 = value; value = tmp;

            //    index = FIELDS_STORE_SIZE;
            //}

            //ensureCapacity(N + 1);
            //if (index != N)
            //{
            //    Array.Copy(data, index - FIELDS_STORE_SIZE,
            //                     data, index - FIELDS_STORE_SIZE + 1,
            //                     N - index);
            //    //System.arraycopy(data, index - FIELDS_STORE_SIZE,
            //    //                 data, index - FIELDS_STORE_SIZE + 1,
            //    //                 N - index);
            //}
            //data[index - FIELDS_STORE_SIZE] = value;


            size1 = N + 1;
            
        }

        public void remove(int index)
        {
            int N = size1;
            if (!(0 <= index && index < N)) throw onInvalidIndex(index, N);
            if (sealed1) throw onSeledMutation();
            --N;
            switch (index)
            {
                case 0:
                    if (N == 0) { f0 = null; break; }
                    f0 = f1;
                    goto case 1;
                case 1:
                    if (N == 1) { f1 = null; break; }
                    f1 = f2;
                    goto case 2;
                case 2:
                    if (N == 2) { f2 = null; break; }
                    f2 = f3;
                    goto case 3;
                case 3:
                    if (N == 3) { f3 = null; break; }
                    f3 = f4;
                    goto case 4;
                case 4:
                    if (N == 4) { f4 = null; break; }
                    f4 = data[0];

                    index = FIELDS_STORE_SIZE;
                    goto  default;
                default:
                    if (index != N)
                    {
                        Array.Copy(data, index - FIELDS_STORE_SIZE + 1,
                                         data, index - FIELDS_STORE_SIZE,
                                         N - index);
                        //System.arraycopy(data, index - FIELDS_STORE_SIZE + 1,
                        //                 data, index - FIELDS_STORE_SIZE,
                        //                 N - index);
                    }
                    data[N - FIELDS_STORE_SIZE] = null;
                    break;
            }
            size1 = N;
   

            //int N = size;
            //if (!(0 <= index && index < N)) throw onInvalidIndex(index, N);
            //if (sealed) throw onSeledMutation();
            //--N;
            //switch (index) {
            //    case 0:
            //        if (N == 0) { f0 = null; break; }
            //        f0 = f1;
            //    case 1:
            //        if (N == 1) { f1 = null; break; }
            //        f1 = f2;
            //    case 2:
            //        if (N == 2) { f2 = null; break; }
            //        f2 = f3;
            //    case 3:
            //        if (N == 3) { f3 = null; break; }
            //        f3 = f4;
            //    case 4:
            //        if (N == 4) { f4 = null; break; }
            //        f4 = data[0];

            //        index = FIELDS_STORE_SIZE;
            //    default:
            //        if (index != N) {
            //            System.arraycopy(data, index - FIELDS_STORE_SIZE + 1,
            //                             data, index - FIELDS_STORE_SIZE,
            //                             N - index);
            //        }
            //        data[N - FIELDS_STORE_SIZE] = null;
            //}
            //size = N;
        }

        public void clear()
        {
            if (sealed1) throw onSeledMutation();
            int N = size1;
            for (int i = 0; i != N; ++i)
            {
                setImpl(i, null);
            }
            size1 = 0;
        }

        public Object[] toArray()
        {
            Object[] array = new Object[size1];
            toArray(array, 0);
            return array;
        }

        public void toArray(Object[] array)
        {
            toArray(array, 0);
        }

        public void toArray(Object[] array, int offset)
        {
            int N = size1;
            switch (N)
            {
                default:
                    Array.Copy(data, 0, array, offset + FIELDS_STORE_SIZE,
                                     N - FIELDS_STORE_SIZE);
                    break;
                //System.arraycopy(data, 0, array, offset + FIELDS_STORE_SIZE,
                //                 N - FIELDS_STORE_SIZE);
                case 5: array[offset + 4] = f4; goto case 4;
                case 4: array[offset + 3] = f3; goto case 3;
                case 3: array[offset + 2] = f2; goto case 2;
                case 2: array[offset + 1] = f1; goto case 1;
                case 1: array[offset + 0] = f0; goto case 0;
                case 0: break;
            }
            
        }

        private void ensureCapacity(int minimalCapacity)
        {
            int required = minimalCapacity - FIELDS_STORE_SIZE;
            if (required <= 0) throw new Exception();
            if (data == null)
            {
                int alloc = FIELDS_STORE_SIZE * 2;
                if (alloc < required)
                {
                    alloc = required;
                }
                data = new Object[alloc];
            }
            else
            {
                int alloc = data.Length;
                if (alloc < required)
                {
                    if (alloc <= FIELDS_STORE_SIZE)
                    {
                        alloc = FIELDS_STORE_SIZE * 2;
                    }
                    else
                    {
                        alloc *= 2;
                    }
                    if (alloc < required)
                    {
                        alloc = required;
                    }
                    Object[] tmp = new Object[alloc];
                    if (size1 > FIELDS_STORE_SIZE)
                    {
                        Array.Copy(data, 0, tmp, 0,
                                         size1 - FIELDS_STORE_SIZE);
                        //System.arraycopy(data, 0, tmp, 0,
                        //                 size1 - FIELDS_STORE_SIZE);
                    }
                    data = tmp;
                }
            }
        }

        private static Exception onInvalidIndex(int index, int upperBound)
        {
            // \u2209 is "NOT ELEMENT OF"
            String msg = index + " \u2209 [0, " + upperBound + ')';
            throw new Exception(msg);
        }

        private static Exception onEmptyStackTopRead()
        {
            throw new Exception("Empty stack");
        }

        private static Exception onSeledMutation()
        {
            throw new Exception("Attempt to modify sealed array");
        }

        private void writeObject(object os)
        {
            //os.defaultWriteObject();
            //int N = size1;
            //for (int i = 0; i != N; ++i)
            //{
            //    Object obj = getImpl(i);
            //    os.writeObject(obj);
            //}
        }

        private void readObject(object is1)
        {
            //    private void readObject(ObjectInputStream is)
            //    throws IOException, ClassNotFoundException
            //{
            //    is.defaultReadObject(); // It reads size
            //    int N = size;
            //    if (N > FIELDS_STORE_SIZE) {
            //        data = new Object[N - FIELDS_STORE_SIZE];
            //    }
            //    for (int i = 0; i != N; ++i) {
            //        Object obj = is.readObject();
            //        setImpl(i, obj);
            //    }
            //}
            //is1.defaultReadObject(); // It reads size
            //int N = size1;
            //if (N > FIELDS_STORE_SIZE) {
            //    data = new Object[N - FIELDS_STORE_SIZE];
            //}
            //for (int i = 0; i != N; ++i) {
            //    Object obj = is1.readObject();
            //    setImpl(i, obj);
            //}
            //TODO:这时要使用对象流
        }

        // Number of data elements

    }
}
